import binascii
import os
# following imports are required by PKI
import Crypto
import Crypto.Random
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5  # 用于签名
from Crypto.Hash import SHA  # 用于签名
import hashlib  # 用于hash计算
import configparser  # 处理ini文件
import time
import datetime
import base64
import json  # 处理JSON数据

# region #定义数据结构
dsTranPart1 = {
    'sender': '',
    'recipient': '',
    'value': 0,
    'timestamp': ''
}  # 转账交易的数据结构1
dsTranPart2 = {
    'part1Str': '',
    'signStr': '',
    'pubkeyStr': ''
}  # 转账交易的数据结构2
dsTranPart3 = {
    'part2Str': '',
    'tranHash': ''
}  # 转账交易的数据结构3
dsBlockPart1 = {
    'blockId': '',
    'previousBlockHash': 0,
    'blockTranStr': [],
    'minerAddress': '',
    'miningRandomNum': 0,
    'miningMsg': '',
    'miningDifficulty': 0,
    'timestamp': ''
}  # 区块的前一部分，不带该区块的HASH值
dsBlockPart2 = {
    'part1Str': '',
    'thisBlockHash': ''
}  # 整个区块部分
miningDifficulty = 10  # 挖矿难度，表示HASH最前面至少要有多少个0
DftTheta = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF >> miningDifficulty  # 右移
# print(hex(DftTheta))
# endregion

# region #定义文件路径
txtFileName = 'TranscationLists.txt'  # 用于存储交易信息
rootPath = os.path.abspath('.')  # current dir
# blockDir = blockRootPath + '/SwuBlockchain/block'  # block file directory
blockDir = rootPath + '/block'  # block file directory
if not os.path.exists(blockDir):
    os.makedirs(blockDir)
accountDir = rootPath + '/account'  # block file directory
if not os.path.exists(accountDir):
    os.makedirs(accountDir)
# endregion


def findBlocFiles(filesPath):  # 查找指定文件夹下的区块链文件
    # 查找文件代码
    if not os.path.exists(filesPath):
        os.makedirs(filesPath)
    files = os.listdir(filesPath)
    filesList = []
    for s in files:
        sPath = os.path.join(filesPath, s)
        if os.path.isdir(sPath):
            findBlocFiles(sPath, filesList)
        elif os.path.isfile(sPath) and 'block' in s:
            filesList.append(sPath)
    return filesList


def findTranFiles(filesPath):  # 查找指定文件夹下的交易
    # 查找文件代码
    if not os.path.exists(filesPath):
        os.makedirs(filesPath)
    files = os.listdir(filesPath)
    filesList = []
    for s in files:
        sPath = os.path.join(filesPath, s)
        if os.path.isdir(sPath):
            findTranFiles(sPath, filesList)
        elif os.path.isfile(sPath) and 'tran' in s:
            filesList.append(sPath)
    return filesList


def GetLastBlockInfo():  # 找到最后一个区块的ID和Hash值
    fList = findBlocFiles(blockDir)
    fCount = len(fList)
    if fCount == 0:
        # 生产0号/创世区块
        blockId = 0
        previousBlockHash = 0
        blockTranStr = '-'
        timestamp = int(time.time())
        miner = '-'
        blockStr1 = "blockId:%s,previousBlockHash:%s,blockTranStr:%s,miner:%s,timestamp:%s" % (
            blockId, previousBlockHash, blockTranStr, miner, timestamp)
        blockHash = Crypto.Hash.SHA256.new()
        blockHash.update(blockStr1.encode('utf-8'))
        blockHashStr = blockHash.hexdigest()
        blockStr2 = "%s,blockHash:%s\n\n\n" % (blockStr1, blockHashStr)
        newBlockFile = r"%s\block%s.txt" % (blockDir, blockId)
        f = open(newBlockFile, "a+")
        f.write(blockStr2)
        f.close()
    else:
        blockId = fCount - 1
        blockFile = r"%s\block%s.txt" % (blockDir, blockId)
        blockHashStr = ''
        f = open(blockFile, "r")
        strs = f.read()
        f.close()
        strs = strs.split('\n\n\n')
        for str in strs:
            list1 = str.split(',')
            for lStr in list1:
                if lStr.split(":")[0] == 'blockHash':
                    blockHashStr = lStr.split(":")[1]

    return blockId, blockHashStr


def VerifyBlock(blockFileName):
    # 从交易文件中导入交易信息
    f = open(blockFileName, "r")
    str1 = f.read()  # 每个区块文件只包含一笔交易
    f.close()
    rstValue = False
    if len(str1) > 0:
        rstValue = False
        _dsBlockPart2 = json.loads(str1)
        calHash = Crypto.Hash.SHA256.new()
        calHash.update(_dsBlockPart2['part1Str'].encode('utf-8'))
        calHashStr = calHash.hexdigest()
        if calHashStr == _dsBlockPart2['thisBlockHash']:
            rstValue = True
        # 只验证了本区块的HASH值是否合法，还可以
        # 验证该区块指向的上一区块指针是否合法
        # 验证区块的难度是否合法
        # 验证区块时间戳是否合法
        # coinbase是否合法
        # 验证每一笔交易是否合法
    return rstValue


def GetAccountCreditOld(address=''):
    f = open(txtFileName, "r")
    tranStr = f.read()
    f.close()
    strs = tranStr.split('\n\n')
    rstValue = 0
    for str in strs:
        if len(str) > 0:
            list1 = str.split(',')
            sender = list1[0].split(":")[1]
            recipient = list1[1].split(":")[1]
            valueStr = list1[2].split(":")[1]
            timestampStr = list1[3].split(":")[1]
            signStr = list1[4].split(":")[1]
            pubkey = list1[5].split(":")[1]
            if address == recipient:
                rstValue += float(valueStr)
    print("账户:%s 的余额为:%.8f" % (address, rstValue))
    return rstValue


def GetAccountCredit(address=''):
    rstValue = 0  # 余额
    fList = findBlocFiles(blockDir)
    fCount = len(fList)
    # 计算每一个区块中的账户信息
    for i in range(1, fCount, 1):
        blocFilename = blockDir + "/block%d.txt" % i
        vResult = VerifyBlock(blocFilename)
        if vResult:
            f = open(blocFilename, "r")
            str1 = f.read()  # 每个区块文件只包含一笔交易
            f.close()
            if len(str1) > 0:
                _dsBlockPart2 = json.loads(str1)
                _dsBlockPart1 = json.loads(_dsBlockPart2['part1Str'])
                listTranStrs = _dsBlockPart1.get('blockTranStr')  # 将各笔交易分开
                for aStr in listTranStrs:  # 针对每一笔交易进行处理
                    _dsTranPart3 = json.loads(aStr)
                    _dsTranPart2 = json.loads(_dsTranPart3['part2Str'])
                    _dsTranPart1 = json.loads(_dsTranPart2['part1Str'])
                    if _dsTranPart1['sender'] == address:
                        rstValue -= float(_dsTranPart1['value'])  # 挖矿的奖励
                    if _dsTranPart1['recipient'] == address:
                        rstValue += float(_dsTranPart1['value'])  # 挖矿的奖励

    print("账户:%s 的余额为:%.8f" % (address, rstValue))
    return rstValue


class Student:
    Name = ''
    address = ''
    Credits = 20.0
    private_key = ''  # 修改为可以外部访问
    public_key = ''  # 修改为可以外部访问

    def __init__(self, stuName):
        PrivateFile = accountDir+"/%s_Private.pem" % (stuName)
        if os.path.exists(PrivateFile):
            # 导入已有账户的私钥
            with open(PrivateFile, "rb") as f:
                privatePemStr = f.read()
                self.private_key = Crypto.PublicKey.RSA.importKey(
                    privatePemStr)
        else:
            # 创建新的账户
            random = Crypto.Random.new().read
            self.private_key = RSA.generate(1024, random)
            # 保存密钥
            with open(PrivateFile, "wb") as f:
                privatePemStr = self.private_key.exportKey()   # 导出私钥
                f.write(privatePemStr)
                f.close()

        publickFile = accountDir+"/%s_Public.pem" % (stuName)
        if os.path.exists(publickFile):
            # 导入已有账户的公钥
            with open(publickFile, "rb") as f:
                publicPemStr = f.read()
                self.public_key = Crypto.PublicKey.RSA.importKey(publicPemStr)
        else:
            self.public_key = self.private_key.publickey()
            publicPemStr = self.public_key.exportKey()   # 导出公钥
            with open(publickFile, "wb") as f:
                f.write(publicPemStr)
        # 计算出账户
        str1 = self.public_key.exportKey(format='PEM').decode('utf-8')
        h1 = Crypto.Hash.SHA256.new(str1.encode('utf-8'))
        str2 = binascii.hexlify(h1.digest()).decode('utf-8')
        h2 = Crypto.Hash.SHA256.new(str2.encode('utf-8'))
        str3 = binascii.hexlify(h2.digest()).decode('utf-8')
        self.address = str3.upper()
        print("Account is ready: %s" % (self.address))


class Miner(Student):  # 继承
    # 铸币交易转账，给矿工提供奖励,转账人为coinbase，没有签名
    def CoinbaseTransfer(self, minerAddress, value):
        self.timestamp = int(time.time())
        _dsTranPart1 = dsTranPart1.copy()
        _dsTranPart1['sender'] = 'coinbase'
        _dsTranPart1['recipient'] = minerAddress
        _dsTranPart1['value'] = value
        _dsTranPart1['timestamp'] = self.timestamp

        _dsTranPart2 = dsTranPart2.copy()
        _dsTranPart2['part1Str'] = json.dumps(_dsTranPart1)
        _dsTranPart2['signStr'] = ''
        _dsTranPart2['pubkeyStr'] = ''

        _dsTranPart3 = dsTranPart3.copy()
        _dsTranPart3['part2Str'] = json.dumps(_dsTranPart2)
        _dsTranPart3['tranHash'] = ''
        strReturn = json.dumps(_dsTranPart3)
        return strReturn

    # 矿工，负责验证交易，并把验证通过的交易记录到账本中
    def VerifyTransaction(self, tranFilename):
        # 构造转账给矿工的交易，实现矿工奖励
        coinbaseTranStr = self.CoinbaseTransfer(self.address, 100)  # 铸币交易字符串
        # fullTranStrs为完整的交易字符串，里边包含coinbase和其他的多笔转账交易
        fullTranStrs = [coinbaseTranStr]

        # region #从交易文件中导入交易信息
        TransactionList = []
        f = open(tranFilename, "r")
        str1 = f.read()
        f.close()
        os.remove(tranFilename)

        TransactionList.append(str1)
        # endregion
        # 验证TransactionList中的交易记录，如果正确则保存到交易记录账本中
        boolVerified = True

        for aStr in TransactionList:
            if len(aStr) > 0:
                _dsTranPart3 = json.loads(aStr)
                _dsTranPart2 = json.loads(_dsTranPart3['part2Str'])
                _dsTranPart1 = json.loads(_dsTranPart2['part1Str'])
                # 验证交易HASH是否正确
                trnHash = Crypto.Hash.SHA256.new()
                trnHash.update(_dsTranPart3['part2Str'].encode('utf-8'))
                tranHashStr = trnHash.hexdigest()
                if _dsTranPart3['tranHash'] == tranHashStr:
                    # 交易HASH验证通过
                    pubkeyStr = _dsTranPart2['pubkeyStr']
                    signStr = _dsTranPart2['signStr']
                    pubkeyPem = base64.b32decode(pubkeyStr)
                    keyPub = Crypto.PublicKey.RSA.importKey(pubkeyPem)
                    tranVerifer = Crypto.Signature.PKCS1_v1_5.new(keyPub)
                    calTanHash = Crypto.Hash.SHA256.new()
                    calTanHash.update(json.dumps(_dsTranPart1).encode('utf-8'))
                    boolTmp = tranVerifer.verify(
                        calTanHash, base64.b32decode(signStr))  # 使用公钥验证签名
                    if boolTmp:  # 验证通过
                        fullTranStrs.append(json.dumps(_dsTranPart3))
                    else:
                        boolVerified = False  # 有一笔交易为验证通过，则放弃处理

        if boolVerified:  # 支付签名验证通过,开始打包为block
            previousBlockId, previousBlockHash = GetLastBlockInfo()
            blockId = int(previousBlockId) + 1
            dsBlockPart1['blockId'] = blockId
            dsBlockPart1['previousBlockHash'] = previousBlockHash
            dsBlockPart1['blockTranStr'] = fullTranStrs
            dsBlockPart1['minerAddress'] = self.address
            dsBlockPart1['miningMsg'] = 'f'
            dsBlockPart1['miningDifficulty'] = miningDifficulty
            dsBlockPart1['timestamp'] = int(time.time())  # 时间戳
            miningRandomNum = 0
            while miningRandomNum <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:
                dsBlockPart1['miningRandomNum'] = miningRandomNum
                blockStr1 = json.dumps(dsBlockPart1)
                blockHash = Crypto.Hash.SHA256.new()
                blockHash.update(blockStr1.encode('utf-8'))
                blockHashStr = blockHash.hexdigest()
                if int(blockHashStr, 16) <= DftTheta:
                    dsBlockPart2['part1Str'] = blockStr1
                    dsBlockPart2['thisBlockHash'] = blockHashStr
                    blockStr2 = json.dumps(dsBlockPart2)
                    newBlockFile = r"%s\block%s.txt" % (
                        blockDir, blockId)
                    f = open(newBlockFile, "w+")
                    f.write(blockStr2)
                    f.close()
                    break
                miningRandomNum = miningRandomNum + 1
        return


class Transaction:
    def __init__(self, stu):
        self._private_key = stu.private_key
        self._private_key_str = base64.b32encode(
            self._private_key.exportKey(format='PEM'))
        self._public_key = stu.public_key
        self._public_key_str = base64.b32encode(
            self._public_key.exportKey(format='PEM'))
        return

    def Transfer(self, sender, recipient, value):
        self.timestamp = int(time.time())
        print('Transaction finished at %s' % (self.timestamp))
        dsTranPart1['sender'] = sender.address
        dsTranPart1['recipient'] = recipient.address
        dsTranPart1['value'] = value
        dsTranPart1['timestamp'] = self.timestamp
        dsTranPart2['part1Str'] = json.dumps(dsTranPart1)
        signStr = self.SignTransaction(dsTranPart2['part1Str'])
        dsTranPart2['signStr'] = signStr
        dsTranPart2['pubkeyStr'] = self._public_key_str.decode('utf-8')
        dsTranPart3['part2Str'] = json.dumps(dsTranPart2)
        trnHash = Crypto.Hash.SHA256.new()
        trnHash.update(dsTranPart3['part2Str'].encode('utf-8'))
        tranHashStr = trnHash.hexdigest()
        dsTranPart3['tranHash'] = tranHashStr
        str2 = json.dumps(dsTranPart3)
        tranFilename = blockDir + \
            '/tran_%s_%s.txt' % (self.timestamp, tranHashStr)
        self.TransactionSave(tranFilename, str2)
        return

    # 发起转账交易，直接转账到一个地址上
    def TransferToAddress(self, sender, recipientAddress, value):
        self.timestamp = int(time.time())
        print('Transaction finished at %s' % (self.timestamp))
        dsTranPart1['sender'] = sender.address
        dsTranPart1['recipient'] = recipientAddress
        dsTranPart1['value'] = value
        dsTranPart1['timestamp'] = self.timestamp
        dsTranPart2['part1Str'] = json.dumps(dsTranPart1)
        signStr = self.SignTransaction(dsTranPart2['part1Str'])
        dsTranPart2['signStr'] = signStr
        dsTranPart2['pubkeyStr'] = self._public_key_str.decode('utf-8')
        dsTranPart3['part2Str'] = json.dumps(dsTranPart2)
        trnHash = Crypto.Hash.SHA256.new()
        trnHash.update(dsTranPart3['part2Str'].encode('utf-8'))
        tranHashStr = trnHash.hexdigest()
        dsTranPart3['tranHash'] = tranHashStr
        str2 = json.dumps(dsTranPart3)
        tranFilename = blockDir + \
            '/tran_%s_%s.txt' % (self.timestamp, tranHashStr)
        self.TransactionSave(tranFilename, str2)
        return

    # 存储交易信息
    def TransactionSave(self, tranFilename, tranStr):
        # 输入文件名，和交易字符串
        f = open(tranFilename, "a+")
        f.write(tranStr)
        f.close()
        return
    # 返回签名后的字符串, tranStr为源字符串、没有经过hash处理

    def SignTransaction(self, tranStr=''):
        signer = PKCS1_v1_5.new(self._private_key)
        h = Crypto.Hash.SHA256.new(tranStr.encode('utf-8'))
        hStr = h.hexdigest()
        str2 = signer.sign(h)
        str3 = base64.b32encode(str2).decode('utf-8')
        return str3


StuA = Student('A')
StuB = Student('B')
tran = Transaction(StuA)    # StuA发起的交易
tran.Transfer(StuA, StuB, 1.1)
# tran.Transfer(StuA,StuB,1.2)
minerA = Miner('minerA')

fList = findTranFiles(blockDir)
for f in fList:
    minerA.VerifyTransaction(f)

GetAccountCredit(StuA.address)
GetAccountCredit(StuB.address)
GetAccountCredit(minerA.address)
